#ifndef XG_YMAL_CPP
#define XG_YMAL_CPP
//////////////////////////////////////////////////////////////////
#include "../File.h"

#ifndef YAML_INDENT_STEP
#define YAML_INDENT_STEP	"  "
#endif

#define YAML_EXCEPTION(index) throw Exception(XG_DATAERR, stdx::format("parse yaml error at line %d", index + 1))

int YAMLNode::SpaceLength(const string& line)
{
	int i = 0;
	int len = line.length();

	while (i < len && line[i] == ' ') i++;

	if (i < len && line[i] == '#') return len;

	return i;
}
string YAMLNode::SpaceUnit(const vector<string>& content)
{
	for (const string& line : content)
	{
		int len = SpaceLength(line);

		if (len > 0) return line.substr(0, len);
	}

	return YAML_INDENT_STEP;
}
int YAMLNode::Parse(YAMLNode* node, vector<string>& content, const string& indent, const string& step, int line)
{
	int res = 0;
	size_t pos = 0;
	string space = indent;
	size_t linecount = content.size();
	static auto isValue = [](const string& msg, size_t& pos){
		size_t begin = 0;

		while (begin < msg.length())
		{
			pos = msg.find(":", begin);

			if (pos == begin || pos == string::npos) return true;

			int c = msg[pos + 1];
			
			if (c == 0 || c == ' ') return false;

			begin = pos + 1;
		}

		return true;
	};

	for (int i = line; i < linecount; i++)
	{
		string msg = content[i];
		int len = SpaceLength(msg);

		if (len >= msg.length()) continue;

		if (len % step.length()) YAML_EXCEPTION(i);
		if (len > space.length()) YAML_EXCEPTION(i);

		if (len < space.length()) return --i;

		pos = msg.find(" #", len);

		if (pos == string::npos)
		{
			msg = msg.substr(len);
		}
		else
		{
			msg = string(msg.c_str() + len, msg.c_str() + pos);
		}

		if (node->name.empty())
		{
			if (isValue(msg, pos)) YAML_EXCEPTION(i);

			node->type = YAML_VALUE;
			node->name = msg.substr(0, pos);
			node->value = stdx::trim(msg.substr(pos + 1));

			if (node->value.length() > 0) return i;

			if (step.empty()) return linecount;

			space = indent + step;
		}
		else
		{
			if (stdx::startwith(msg, "-"))
			{
				if (node->type == YAML_OBJECT) return XG_DATAERR;

				sp<YAMLNode> item = newsp<YAMLNode>();

				if (msg.length() > 1 && isValue(msg, pos))
				{
					item->value = stdx::trim(msg.substr(1));
					item->type = YAML_VALUE;

					node->children.push_back(item);
					node->type = YAML_ARRAY;
				}
				else
				{
					msg = stdx::trim(msg.substr(1));

					vector<string> backup;

					if (msg.empty())
					{
						backup.push_back(content[i]);
					}
					else
					{
						if (i <= 0) return XG_DATAERR;

						backup.push_back(content[i]);

						content[i--] = space + step + msg;

						backup.push_back(content[i]);
					}

					content[i] = space + "@:";

					res = Parse(item.get(), content, space, step, i);

					if (backup.size() == 1)
					{
						std::swap(content[i], backup[0]);
					}
					else
					{
						std::swap(content[i + 1], backup[0]);
						std::swap(content[i + 0], backup[1]);
					}

					if (res >= i)
					{
						i = res;
						item->name.clear();
						node->type = YAML_ARRAY;
						node->children.push_back(item);
					}
				}
			}
			else
			{
				if (node->type == YAML_ARRAY) return XG_DATAERR;

				if (isValue(msg, pos))
				{
					node->value = stdx::trim(msg);

					return i;
				}

				sp<YAMLNode> item = newsp<YAMLNode>();

				if ((res = Parse(item.get(), content, space, step, i)) >= i)
				{
					node->children.push_back(item);
					node->type = YAML_OBJECT;
					i = res;
				}
			}
		}
	}

	return content.size();
}

string YAMLNode::toString() const
{
	return toString(stdx::EmptyString(), YAML_INDENT_STEP);
}
bool YAMLNode::parse(const string& content)
{
	string step;

	return parse(content, step);
}
bool YAMLNode::parse(const string& content, string& step)
{
	if (content.empty()) return false;

	vector<string> lines = stdx::split(content, "\n");
	int len = lines.size();
	int i = 0;

	for (string& item : lines)
	{
		item = stdx::replace(item, "\t", YAML_INDENT_STEP);
		item = stdx::trim(item, "\r");
	}

	if (step.empty()) step = SpaceUnit(lines);

	for (int i = 0; i < len; i++)
	{
		const string& msg = lines[i];

		if (msg.empty()) continue;

		if (SpaceLength(msg) == 0)
		{
			sp<YAMLNode> item = newsp<YAMLNode>();

			int res = Parse(item.get(), lines, stdx::EmptyString(), step, i);

			if (res < 0) YAML_EXCEPTION(i);

			children.push_back(item);

			i = max(--res, i);
		}
	}

	return children.size() > 0;
}
sp<YAMLNode> YAMLNode::create(const string& path)
{
	auto arr = stdx::split(path, ".");
	sp<YAMLNode> cur = sp<YAMLNode>((YAMLNode*)(this), [](YAMLNode*){});

	for (const string& name : arr)
	{
		sp<YAMLNode> item = cur->child(name);

		if (item)
		{
			cur = item;
		}
		else
		{
			cur->children.push_back(item = newsp<YAMLNode>());
			item->name = name;
			cur = item;
		}
	}

	return cur.get() == this ? NULL : cur;
}
sp<YAMLNode> YAMLNode::get(const string& path) const
{
	auto arr = stdx::split(path, ".");
	sp<YAMLNode> cur = sp<YAMLNode>((YAMLNode*)(this), [](YAMLNode*){});

	for (const string& name : arr)
	{
		if (cur = cur->child(name)) continue;

		return NULL;
	}

	return cur.get() == this ? NULL : cur;
}
sp<YAMLNode> YAMLNode::child(const string& name) const
{
	for (const sp<YAMLNode>& item : children)
	{
		if (name == item->name) return item;
	}

	return NULL;
}
string YAMLNode::toString(const string& indent, const string& step) const
{
	string res;
	string blank = step;
	string space = indent;

	if (blank.empty()) blank = YAML_INDENT_STEP;

	if (name.empty())
	{
		if (space.length() > 0)
		{
			if (value.length() > 0) return space + "- " + value;
			
			res = space + "-";

			space += blank;
		}
	}
	else
	{
		res = space + name + ":";

		if (value.length() > 0) return res + " " + value;

		if (children.empty()) return res;

		space += blank;
	}

	if (children.size() > 0)
	{
		for (const sp<YAMLNode>& item : children)
		{
			res += "\n" + item->toString(space, blank);
		}
	}

	return res[0] == '\n' ? res.substr(1) : std::move(res);
}

bool YAMLoader::reload()
{
	return path.length() > 0 && open(path);
}
bool YAMLoader::canUse() const
{
	return doc.isObject();
}
YAMLoader* YAMLoader::Instance()
{
	XG_DEFINE_GLOBAL_VARIABLE(YAMLoader)
}
string YAMLoader::toString() const
{
	return doc.toString(stdx::EmptyString(), step);
}
bool YAMLoader::open(const string& path)
{
	string msg;
	YAMLNode node;

	CHECK_FALSE_RETURN(stdx::GetFileContent(msg, path) > 0);
	CHECK_FALSE_RETURN(parse(msg, path));
#ifdef XG_LINUX
	this->path = path[0] == '/' ? path : Process::GetCurrentDirectory() + "/" + path;
#else
	this->path = path[1] == ':' ? path : Process::GetCurrentDirectory() + "/" + path;
#endif
	return true;
}
const YAMLNode* YAMLoader::document() const
{
	return &doc;
}
bool YAMLoader::parse(const string& msg, const string& name)
{
	YAMLNode node;

	step.clear();

	try
	{
		CHECK_FALSE_RETURN(node.parse(msg, step));

		std::swap(node, doc);

		return true;
	}
	catch(const Exception& e)
	{
		LogTrace(eERR, "load config[%s] failed[%s]", name.c_str(), e.what());

		return false;
	}
}
bool YAMLoader::raw(const string& path, string& value) const
{
	sp<YAMLNode> node = doc.get(path);

	CHECK_FALSE_RETURN(node);

	value = node->getValue();

	return true;

}
bool YAMLoader::get(const string& path, string& value) const
{
	sp<YAMLNode> node = doc.get(path);

	CHECK_FALSE_RETURN(node);

	value = stdx::translate(node->getValue());

	return true;
}
void YAMLoader::set(const string& path, const string& value)
{
	if (step.empty()) step = YAML_INDENT_STEP;

	sp<YAMLNode> node = doc.create(path);
	node->type = YAML_VALUE;
	node->children.clear();
	node->value = value;
}
bool YAMLoader::list(const string& path, vector<string>& reslist) const
{
	sp<YAMLNode> node = doc.get(path);

	CHECK_FALSE_RETURN(node);

	auto& vec = node->getChildren();

	for (auto& item : vec)
	{
		reslist.push_back(stdx::translate(item->getValue()));
	}

	return true;
}
bool YAMLoader::list(const string& path, map<string, string>& resmap) const
{
	sp<YAMLNode> node = doc.get(path);

	CHECK_FALSE_RETURN(node);

	auto& vec = node->getChildren();

	for (auto& item : vec)
	{
		resmap[item->getName()] = stdx::translate(item->getValue());
	}

	return true;
}

const string& YAMLoader::getFilePath() const
{
	return path;
}
string YAMLoader::raw(const string& path) const
{
	string msg;

	raw(path, msg);

	return std::move(msg);
}
string YAMLoader::get(const string& path) const
{
	string msg;

	get(path, msg);

	return std::move(msg);
}
size_t YAMLoader::capacity(const string& path) const
{
	string msg = stdx::toupper(get(path));

	long long res = stdx::atol(msg.c_str());

	if (res <= 0) return 0;

	if (stdx::endwith(msg, "K") || stdx::endwith(msg, "KB"))
	{
		res *= 1024;
	}
	else if (stdx::endwith(msg, "M") || stdx::endwith(msg, "MB"))
	{
		res *= 1024 * 1024;
	}
	else if (stdx::endwith(msg, "G") || stdx::endwith(msg, "GB"))
	{
		res *= 1024 * 1024 * 1024;
	}

	return (size_t)(res);
}
bool YAMLoader::get(const string& path, int& value) const
{
	long tmp = 0;

	CHECK_FALSE_RETURN(get(path, tmp));

	value = (int)(tmp);

	return true;
}
bool YAMLoader::get(const string& path, bool& value) const
{
	long tmp = 0;

	CHECK_FALSE_RETURN(get(path, tmp));

	value = tmp > 0;

	return true;
}
bool YAMLoader::get(const string& path, long& value) const
{
	string msg;

	CHECK_FALSE_RETURN(get(path, msg));

	if (msg.empty())
	{
		value = 0;
	}
	else if (isalpha(msg.front()))
	{
		value = stdx::tolower(msg) == "true" ? 1 : 0;
	}
	else
	{
		value = stdx::atol(msg.c_str());
	}

	return true;
}
bool YAMLoader::get(const string& path, double& value) const
{
	string msg;

	CHECK_FALSE_RETURN(get(path, msg));

	value = stdx::atof(msg.c_str());

	return true;
}
map<string, string> YAMLoader::list(const string& path) const
{
	map<string, string> resmap;

	list(path, resmap);

	return std::move(resmap);
}

//////////////////////////////////////////////////////////////////
#endif